from __future__ import annotations

import os, sys
from typing import Mapping
from google.protobuf.compiler import plugin_pb2 as plugin
from google.protobuf.descriptor_pb2 import (
    DescriptorProto as MessageDP,
    EnumDescriptorProto as EnumDP,
    FieldDescriptorProto as FieldDP,
    FileDescriptorProto as FileDP,
    ServiceDescriptorProto as ServiceDP,
    OneofDescriptorProto as OneofDP,
)
from .options import parse_options
from .templates import TemplateRenderer
from .descriptor_ext import ProtoFile
from .utils import collect_oneof_fields
from .generator_dts import generate_single_file_dts

# global options
g_disable_const_enum = False
g_enable_clone = False


def output_filename(proto_name: str) -> tuple[str, str, str]:
    # 例：foo/bar.proto -> foo/bar.napi.h, foo/bar.napi.cc, foo/bar_pb.d.ts
    base = os.path.splitext(proto_name)[0]
    return (f"{base}.napi.h", f"{base}.napi.cc", f"{base}_pb.d.ts")


def generate_single_file(
    proto: ProtoFile, file_map: Mapping[str, ProtoFile], renderer: TemplateRenderer
) -> tuple[str, str, str]:
    global g_disable_const_enum, g_enable_clone
    g_disable_const_enum = proto.opts.disable_const_enum
    g_enable_clone = proto.opts.enable_clone
    renderer.clear()
    return (
        _generate_single_file_header(proto, file_map, renderer),
        _generate_single_file_cc(proto, file_map, renderer),
        generate_single_file_dts(proto, file_map, renderer),
    )


def _generate_single_file_header(
    proto: ProtoFile, file_map: Mapping[str, ProtoFile], renderer: TemplateRenderer
) -> str:
    lines: list[str] = []

    # 1. 文件头
    lines.append("// Generated by protoc-gen-napi. DO NOT EDIT!")
    lines.append("// source: " + proto.fd.name)
    lines.append("")

    header_macro = proto.cc_header_guard
    lines.append(f"#ifndef {header_macro}")
    lines.append(f"#define {header_macro}")
    lines.append("")

    # 2. 依赖 import
    for dep in proto.fd.dependency:
        lines.append(f'#include "{file_map[dep].cc_module_name}"')
        lines.append(f'#include "{file_map[dep].cc_napi_name}"')
    lines.append(f'#include "{file_map[proto.fd.name].cc_module_name}"')
    lines.append('#include "napi_utils.h"')
    lines.append('#include "napi/native_api.h"')
    lines.append("")

    pkg_name = proto.package_name
    namespace = pkg_name.replace(".", "::")
    if namespace:
        lines.append(f"namespace {namespace} {{")

    # 3. 枚举
    if g_disable_const_enum:
        for enum in proto.fd.enum_type:
            lines.extend(_generate_enum_header(enum, renderer, pkg_name=pkg_name))
            lines.append("")

    # 4. 消息
    for msg in proto.fd.message_type:
        lines.extend(
            _generate_message_header(
                msg,
                renderer,
                pkg_name=pkg_name,
            )
        )
        lines.append("")

    # 5. 服务
    for svc in proto.fd.service:
        lines.extend(_generate_service_header(svc, renderer))
        lines.append("")

    # 6. 文件级扩展
    for ext, _ in proto.iter_extensions():
        lines.extend(_generate_extension_header(ext, renderer))
        lines.append("")

    # 7. 文件级选项
    if proto.fd.options.ByteSize():
        lines.extend(_generate_file_options_header(proto.fd.options, renderer))

    if namespace:
        lines.append("} // namespace " + namespace)
    lines.append("")
    lines.extend(renderer.one_init_per_header(proto))
    lines.append(f"#endif // {header_macro}")
    return "\n".join(lines)


# ---------------- 枚举 ----------------
def _generate_enum_header(
    enum: EnumDP, renderer: TemplateRenderer, parent_names: list[str] = [], pkg_name: str = ""
) -> list[str]:
    return renderer.enum_stub_header(enum, parent_names)


# ---------------- 消息 ----------------
def _generate_message_header(
    msg: MessageDP,
    renderer: TemplateRenderer,
    parent_names: list[str] = [],
    pkg_name: str = "",
) -> list[str]:
    lines = renderer.message_stub_header(msg, parent_names, pkg_name)
    message_name = renderer.get_type_name_in_cc(msg.name, parent_names)

    # 嵌套枚举
    if g_disable_const_enum:
        for nested_enum in msg.enum_type:
            lines.extend(_generate_enum_header(nested_enum, renderer, parent_names=[*parent_names, message_name], pkg_name=pkg_name))

    # 嵌套消息
    for nested_msg in msg.nested_type:
        if nested_msg.options.map_entry:
            continue
        lines.extend(
            _generate_message_header(
                nested_msg,
                renderer,
                parent_names=[*parent_names, message_name],
                pkg_name=pkg_name,
            )
        )

    return lines


# ---------------- 服务 ----------------
def _generate_service_header(svc: ServiceDP, renderer: TemplateRenderer) -> list[str]:
    return renderer.service_stub(svc)
    # TODO(user): 填充 service_stub 模板


# ---------------- 扩展 ----------------
def _generate_extension_header(ext: FieldDP, renderer: TemplateRenderer) -> list[str]:
    return renderer.extension_stub(ext)
    # TODO(user): 填充 extension_stub 模板


# ---------------- 文件选项 ----------------
def _generate_file_options_header(options, renderer: TemplateRenderer) -> list[str]:
    return renderer.file_options_stub(options)
    # TODO(user): 填充 file_options_stub 模板


def _generate_single_file_cc(proto: ProtoFile, file_map: Mapping[str, ProtoFile], renderer: TemplateRenderer) -> str:
    lines: list[str] = []

    # 1. 文件头
    lines.append("// Generated by protoc-gen-mypy. DO NOT EDIT!")
    lines.append("// source: " + proto.fd.name)
    lines.append("")

    # 2. 依赖 import
    for dep in proto.fd.dependency:
        lines.append(f'#include "{file_map[dep].cc_module_name}"')
    lines.append(f'#include "{file_map[proto.fd.name].cc_napi_name}"')
    lines.append('#include "napi_utils.h"')
    lines.append("")

    pkg_name = proto.fd.package
    namespace = pkg_name.replace(".", "::")
    if namespace:
        lines.append(f"namespace {namespace} {{")

    # 3. 枚举
    if g_disable_const_enum:
        for enum in proto.fd.enum_type:
            lines.extend(_generate_enum(enum, renderer, pkg_name=pkg_name))
            lines.append("")

    # 4. 消息
    for msg in proto.fd.message_type:
        lines.extend(
            _generate_message(
                msg,
                renderer,
                pkg_name=pkg_name,
            )
        )
        lines.append("")

    # 5. 服务
    for svc in proto.fd.service:
        lines.extend(_generate_service(svc, renderer))
        lines.append("")

    # 6. 文件级扩展
    for ext, _ in proto.iter_extensions():
        lines.extend(_generate_extension(ext, renderer))
        lines.append("")

    # 7. 文件级选项
    if proto.fd.options.ByteSize():
        lines.extend(_generate_file_options(proto.fd.options, renderer))

    if namespace:
        lines.append("} // namespace " + namespace)

    return "\n".join(lines)


# ---------------- 枚举 ----------------
def _generate_enum(
    enum: EnumDP, renderer: TemplateRenderer, parent_names: list[str] = [], pkg_name: str = ""
) -> list[str]:
    return renderer.enum_stub(enum, parent_names, pkg_name)


# ---------------- 消息 ----------------
def _generate_message(
    msg: MessageDP,
    renderer: TemplateRenderer,
    parent_names: list[str] = [],
    pkg_name: str = "",
) -> list[str]:
    message_name = renderer.get_type_name_in_cc(msg.name, parent_names)
    lines = renderer.message_stub(msg, parent_names, pkg_name)
    # TODO(user): 填充 message_stub 模板

    # 嵌套枚举
    if g_disable_const_enum:
        for nested_enum in msg.enum_type:
            lines.extend(_generate_enum(nested_enum, renderer, parent_names=[*parent_names, message_name], pkg_name=pkg_name))

    # 嵌套消息
    for nested_msg in msg.nested_type:
        if nested_msg.options.map_entry:
            continue
        lines.extend(_generate_message(nested_msg, renderer, parent_names=[*parent_names, message_name], pkg_name=pkg_name))

    lines.extend(renderer.message_init_stub(msg, parent_names, pkg_name))
    lines.extend(renderer.message_init_by_napi_obj_stub(msg, parent_names, pkg_name))
    lines.extend(renderer.message_ctor_stub(msg, parent_names, pkg_name))
    lines.extend(renderer.message_from_binary_stub(msg, parent_names, pkg_name))
    lines.extend(renderer.message_to_binary_stub(msg, parent_names, pkg_name))
    if g_enable_clone:
        lines.extend(renderer.message_clone_stub(msg, parent_names, pkg_name))

    # 字段
    for field in msg.field:
        lines.extend(_generate_field(field, renderer, message_name, pkg_name))

    # oneof
    oneof_fields = collect_oneof_fields(msg)
    for oneof, fields in oneof_fields:
        lines.extend(_generate_oneof(oneof, fields, renderer, message_name, pkg_name))

    return lines


# ---------------- 字段 ----------------
def _generate_field(
    field: FieldDP, renderer: TemplateRenderer, msg_name: str, pkg_name: str
) -> list[str]:
    return renderer.field_stub(field, msg_name, pkg_name)
    # TODO(user): 填充 field_stub 模板


# ---------------- oneof ----------------
def _generate_oneof(
    oneof: OneofDP,
    fields: list[FieldDP],
    renderer: TemplateRenderer,
    msg_name: str,
    pkg_name: str,
) -> list[str]:
    return renderer.oneof_stub(oneof, fields, msg_name, pkg_name)
    # TODO(user): 填充 oneof_stub 模板


# ---------------- 服务 ----------------
def _generate_service(svc: ServiceDP, renderer: TemplateRenderer) -> list[str]:
    return renderer.service_stub(svc)
    # TODO(user): 填充 service_stub 模板


# ---------------- 扩展 ----------------
def _generate_extension(ext: FieldDP, renderer: TemplateRenderer) -> list[str]:
    return renderer.extension_stub(ext)
    # TODO(user): 填充 extension_stub 模板


# ---------------- 文件选项 ----------------
def _generate_file_options(options, renderer: TemplateRenderer) -> list[str]:
    return renderer.file_options_stub(options)
    # TODO(user): 填充 file_options_stub 模板
